import {
  document,
	window,
	HTMLCanvasElement,
	requestAnimationFrame,
	cancelAnimationFrame,
core,
	Event,
  Event0
} from "dhtml-weixin"
import * as THREE from './three/Three';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { OrbitControls0 } from 'three/addons/controls/OrbitControls0.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
import { ProgressiveLightMap } from './three/addons/misc/ProgressiveLightMap.js';

var requestId
Page({
  onShareAppMessage(){
    return getApp().onShare()
  },
  onShareTimeline(){
     return {title:"ThreeX 2.0"}
  },
	onUnload() {
		cancelAnimationFrame(requestId, this.canvas)
		this.worker && this.worker.terminate()
if(this.canvas) this.canvas = null
		setTimeout(() => {
			if (this.renderer instanceof THREE.WebGLRenderer) {
				this.renderer.dispose()
				this.renderer.forceContextLoss()
				this.renderer.context = null
				this.renderer.domElement = null
				this.renderer = null
			}
		}, 0)
	},
  webgl_touch(e){
		const web_e = (window.platform=="devtools"?Event:Event0).fix(e)
		this.canvas.dispatchEvent(web_e)
  },
  onLoad() {
		document.createElementAsync("canvas", "webgl2").then(canvas => {
      this.canvas = canvas
      this.body_load(canvas).then()
    })
  },
  async body_load(canvas3d) {
  // ShadowMap + LightMap Res and Number of Directional Lights
  const shadowMapRes = 512, lightMapRes = 1024, lightCount = 8;
  let camera, scene, renderer, controls, control, control2,
    object = new THREE.Mesh(), lightOrigin = null, progressiveSurfacemap;
  const dirLights = [], lightmapObjects = [];
  const params = { 'Enable': true, 'Blur Edges': true, 'Blend Window': 200,
           'Light Radius': 50, 'Ambient Weight': 0.5, 'Debug Lightmap': false };
  init();
  createGUI();
  animate();

  function init() {

    // renderer
    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.shadowMap.enabled = true;
    document.body.appendChild( renderer.domElement );

    // camera
    camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
    camera.position.set( 0, 100, 200 );
    camera.name = 'Camera';

    // scene
    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0x949494 );
    scene.fog = new THREE.Fog( 0x949494, 1000, 3000 );

    // progressive lightmap
    progressiveSurfacemap = new ProgressiveLightMap( renderer, lightMapRes );

    // directional lighting "origin"
    lightOrigin = new THREE.Group();
    lightOrigin.position.set( 60, 150, 100 );
    scene.add( lightOrigin );

    // transform gizmo
    control = new TransformControls( camera, renderer.domElement );
    control.addEventListener( 'dragging-changed', ( event ) => {

      controls.enabled = ! event.value;

    } );
    control.attach( lightOrigin );
    scene.add( control );

    // create 8 directional lights to speed up the convergence
    for ( let l = 0; l < lightCount; l ++ ) {

      const dirLight = new THREE.DirectionalLight( 0xffffff, 1.0 / lightCount );
      dirLight.name = 'Dir. Light ' + l;
      dirLight.position.set( 200, 200, 200 );
      dirLight.castShadow = true;
      dirLight.shadow.camera.near = 100;
      dirLight.shadow.camera.far = 5000;
      dirLight.shadow.camera.right = 150;
      dirLight.shadow.camera.left = - 150;
      dirLight.shadow.camera.top = 150;
      dirLight.shadow.camera.bottom = - 150;
      dirLight.shadow.mapSize.width = shadowMapRes;
      dirLight.shadow.mapSize.height = shadowMapRes;
      lightmapObjects.push( dirLight );
      dirLights.push( dirLight );

    }

    // ground
    const groundMesh = new THREE.Mesh(
      new THREE.PlaneGeometry( 600, 600 ),
      new THREE.MeshPhongMaterial( { color: 0xffffff, depthWrite: true } )
    );
    groundMesh.position.y = - 0.1;
    groundMesh.rotation.x = - Math.PI / 2;
    groundMesh.name = 'Ground Mesh';
    lightmapObjects.push( groundMesh );
    scene.add( groundMesh );

    // model
    function loadModel() {

      object.traverse( function ( child ) {

        if ( child.isMesh ) {

          child.name = 'Loaded Mesh';
          child.castShadow = true;
          child.receiveShadow = true;
          child.material = new THREE.MeshPhongMaterial();

          // This adds the model to the lightmap
          lightmapObjects.push( child );
          progressiveSurfacemap.addObjectsToLightMap( lightmapObjects );

        } else {

          child.layers.disableAll(); // Disable Rendering for this

        }

      } );
      scene.add( object );
      object.scale.set( 2, 2, 2 );
      object.position.set( 0, - 16, 0 );
      control2 = new TransformControls( camera, renderer.domElement );
      control2.addEventListener( 'dragging-changed', ( event ) => {

        controls.enabled = ! event.value;

      } );
      control2.attach( object );
      scene.add( control2 );
      const lightTarget = new THREE.Group();
      lightTarget.position.set( 0, 20, 0 );
      for ( let l = 0; l < dirLights.length; l ++ ) {

        dirLights[ l ].target = lightTarget;

      }

      object.add( lightTarget );

      if ( typeof TESTING !== 'undefined' ) {

        for ( let i = 0; i < 300; i ++ ) {

          render();

        }

      }

    }

    const manager = new THREE.LoadingManager( loadModel );
    const loader = new GLTFLoader( manager );
    loader.load( 'models/gltf/ShadowmappableMesh.glb', function ( obj ) {

      object = obj.scene.children[ 0 ];

    } );

    // controls
    controls = new (window.platform=="devtools"?OrbitControls:OrbitControls0)( camera, renderer.domElement );
    controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
    controls.dampingFactor = 0.05;
    controls.screenSpacePanning = true;
    controls.minDistance = 100;
    controls.maxDistance = 500;
    controls.maxPolarAngle = Math.PI / 1.5;
    controls.target.set( 0, 100, 0 );
    window.addEventListener( 'resize', onWindowResize );

  }

  function createGUI() {

    const gui = new GUI( { title: 'Accumulation Settings' } );
    gui.add( params, 'Enable' );
    gui.add( params, 'Blur Edges' );
    gui.add( params, 'Blend Window', 1, 500 ).step( 1 );
    gui.add( params, 'Light Radius', 0, 200 ).step( 10 );
    gui.add( params, 'Ambient Weight', 0, 1 ).step( 0.1 );
    gui.add( params, 'Debug Lightmap' );

  }

  function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize( window.innerWidth, window.innerHeight );

  }

  function render() {

    // Update the inertia on the orbit controls
    controls.update();

    // Accumulate Surface Maps
    if ( params[ 'Enable' ] ) {

      progressiveSurfacemap.update( camera, params[ 'Blend Window' ], params[ 'Blur Edges' ] );

      if ( ! progressiveSurfacemap.firstUpdate ) {

        progressiveSurfacemap.showDebugLightmap( params[ 'Debug Lightmap' ] );

      }

    }

    // Manually Update the Directional Lights
    for ( let l = 0; l < dirLights.length; l ++ ) {

      // Sometimes they will be sampled from the target direction
      // Sometimes they will be uniformly sampled from the upper hemisphere
      if ( Math.random() > params[ 'Ambient Weight' ] ) {

        dirLights[ l ].position.set(
          lightOrigin.position.x + ( Math.random() * params[ 'Light Radius' ] ),
          lightOrigin.position.y + ( Math.random() * params[ 'Light Radius' ] ),
          lightOrigin.position.z + ( Math.random() * params[ 'Light Radius' ] ) );

      } else {

        // Uniform Hemispherical Surface Distribution for Ambient Occlusion
        const lambda = Math.acos( 2 * Math.random() - 1 ) - ( 3.14159 / 2.0 );
        const phi = 2 * 3.14159 * Math.random();
        dirLights[ l ].position.set(
                  ( ( Math.cos( lambda ) * Math.cos( phi ) ) * 300 ) + object.position.x,
          Math.abs( ( Math.cos( lambda ) * Math.sin( phi ) ) * 300 ) + object.position.y + 20,
                    ( Math.sin( lambda ) * 300 ) + object.position.z
        );

      }

    }

    // Render Scene
    renderer.render( scene, camera );

  }

  function animate() {

    requestId = requestAnimationFrame( animate );
    render();

  }
  }
})